import { paraTagNames, autoCloseTagNames } from './config';
import { ensureSelectContent } from './assert';
import { 
  setStartBeforeFirstContent,
  setEndAfterLastContent
} from './range';
import { findLCA, findPara } from './find';

function getTextFromNodeOrFrag(frag) {
  if(!(frag instanceof DocumentFragment) && !(frag instanceof Node)) {
    console.error(`get text from fragment: argument-${frag} is not a fragment or node, return empty string`);
    return '';
  }

  let text = '';

  frag.childNodes.forEach(node => {
    if(node.nodeType === Node.TEXT_NODE) {
      text = text.concat(node.nodeValue);
    } else if(node.nodeType === Node.ELEMENT_NODE) {
      text = text.concat(getTextFromNodeOrFrag(node));
    }
  });

  if(frag instanceof Node && frag.nodeType === Node.ELEMENT_NODE && paraTagNames.includes(frag.tagName)) {
    text = text.concat('\n');
  }

  return text;
}

function getTextNodesFromNodeOrFrag(node) {
  if(!(node instanceof Node) && !(node instanceof DocumentFragment)) {
    console.warn(`getTextNodesFronNodeOrFrag: node-${node} parameter error, return []`);
    return [];
  }
  let textNodes = [];
  node.childNodes.forEach(child => {
    if(child.nodeType === Node.TEXT_NODE) {
      textNodes.push(child);
    } else if(child.nodeType === Node.ELEMENT_NODE) {
      textNodes = textNodes.concat(getTextNodesFromNodeOrFrag(child));
    }
  });
  return textNodes;
}

function getParaListFromNodeOrFrag(node) {
  if(!node) return [];
  if(node.nodeType !== Node.ELEMENT_NODE && !(node instanceof DocumentFragment)) return [];
  function getParaListHelp(node, paras = []) {
    if(paraTagNames.includes(node.tagName)) paras.push(node);
    else {
      node.childNodes.forEach(node => {
        getParaListHelp(node, paras);
      })
    }
  }
  let paras = [];
  getParaListHelp(node, paras);
  return paras;
}

function getContentNodesFromNodeOrFrag(node) {
  if(!node) {
    console.warn(`getContentNodes: node-${node} is not a node`);
    return [];
  }
  if(node.nodeType !== Node.ELEMENT_NODE && !(node instanceof DocumentFragment)){
    console.warn(`getContentNodes: node-${node} is not a node`);
    return [];
  } 
  function getContentNodesHelp(node, texts = []) {
    if(node.nodeType === Node.TEXT_NODE) texts.push(node);
    else if(node.nodeType === Node.ELEMENT_NODE && autoCloseTagNames.includes(node.tagName)) {
      texts.push(node);
    } else {
      node.childNodes.forEach(node => {
        getContentNodesHelp(node, texts);
      })
    }
  }
  let texts = [];
  getContentNodesHelp(node, texts);
  return texts;
}

function getContentNodesFromRange(range) {
  ensureSelectContent(range);
  let sc = range.startContainer, ec = range.endContainer,
    so = range.startOffset, eo = range.endOffset;
  let ranges = [];

  if(sc === ec) {
    ranges.push(range.cloneRange());
  } else {
    let head = new Range();
    head.setStart(sc, so);
    setEndAfterLastContent(head, sc);
    ranges.push(head);

    let LCA = findLCA(sc, ec);
    let texts = getContentNodesFromNodeOrFrag(LCA);
    let startIdx = texts.indexOf(sc), endIdx = texts.indexOf(ec);
    for(let i = startIdx + 1; i < endIdx; i++) {
      let bodyText = new Range();
      bodyText.selectNodeContents(texts[i]);
      ranges.push(bodyText);
    }

    let tail = new Range();
    tail.setEnd(ec, eo);
    setStartBeforeFirstContent(tail, ec);
    ranges.push(tail);
  }

  return ranges;
}

function getTextNodesFromRange(range) {
  ensureSelectContent(range);
  let sc = range.startContainer, ec = range.endContainer,
    so = range.startOffset, eo = range.endOffset;
  let ranges = [];

  if(sc === ec) {
    ranges.push(range.cloneRange());
  } else {
    let head = new Range();
    head.setStart(sc, so);
    setEndAfterLastContent(head, sc);
    ranges.push(head);

    let LCA = findLCA(sc, ec);
    let texts = getContentNodesFromNodeOrFrag(LCA);
    let startIdx = texts.indexOf(sc), endIdx = texts.indexOf(ec);
    for(let i = startIdx + 1; i < endIdx; i++) {
      let bodyText = new Range();
      bodyText.selectNodeContents(texts[i]);
      ranges.push(bodyText);
    }

    let tail = new Range();
    tail.setEnd(ec, eo);
    setStartBeforeFirstContent(tail, ec);
    ranges.push(tail);
  }

  return ranges;
}

function getBoundLetterFromRange(range) {
  if(!(range instanceof Range)) {
    console.warn(`getBoundLetterFromRange: range-${range} parameter error return [null, null]`);
    return [null, null];
  }
  if(range.toString()) {
    console.warn(`getBoundLetterFromRange: range-${range}.toString() is not null`);
  }
  if(range.startContainer.nodeType !== Node.TEXT_NODE || range.endContainer.nodeType !== Node.TEXT_NODE) {
    console.warn(`getBoundLetterFromRange: please normalize range first, return [null, null]`);
    return [null, null];
  }
  let res = [null, null];
  if(range.startOffset === 0) res[0] = '';
  else res[0] = range.startContainer.nodeValue[range.startOffset - 1];
  let para = findPara(range.endContainer);
  let ec = range.endContainer, eo = range.endOffset;
  if(ec.nodeValue.length === eo) {
    let textNodes = getTextNodesFromNodeOrFrag(para);
    let idx = textNodes.indexOf(ec);
    if(idx === textNodes.length - 1) {
      res[1] = '';
    } else {
      res[1] = textNodes[idx + 1].nodeValue[0];
    }
  } else {
    res[1] = ec.nodeValue[eo];
  }
  return res;
}

function getTextIndexBaseParaFromRange(range) {
  if(!(range instanceof Range)) {
    console.warn(`getTextIndexBaseParaFromRange: range-${range} is not Range, return -1`);
    return -1;
  }
  if(range.toString()) {
    console.warn(`getTextIndexBaseParaFromRange: range.toString()-${range.toString} is not null, return -1`);
    return -1;
  }
  if(!(range.startContainer.nodeType === Node.TEXT_NODE)) {
    console.warn(`getTextIndexBaseParaFromRange: please normalizeRange first, return -1`);
    return -1;
  }
  let sc = range.startContainer, so = range.startOffset,
    ec = range.endContainer, eo = range.endOffset;
  let sp = findPara(sc), ep = findPara(ec);
  if(sp !== ep) {
    console.warn(`getTextIndexBaseParaFromRange: sp-${sp}, ep-${ep}, multi para`);
  }
  let sTextNodes = getTextNodesFromNodeOrFrag(sp);
  let eTextNodes = getTextNodesFromNodeOrFrag(ep);
  let sidx = 0, eidx = 0;
  if(sTextNodes.indexOf(sc) === -1) {
    console.warn(`getTextIndexBaseParaFromRange: sc-${sc}, para-${sp}, a bug`);
    res[0] = -1;
  }
  for(let i = 0; i < sTextNodes.length; i++) {
    let text = sTextNodes[i];
    if(text === sc) {
      sidx += so;
      break;
    } else {
      sidx += text.nodeValue.length;
    }
  }
  if(eTextNodes.indexOf(ec) === -1) {
    console.warn(`getTextIndexBaseParaFromRange: ec-${ec}, epara-${ep}, a bug`);
    res[1] = -1;
  }
  for(let i = 0; i < eTextNodes.length; i++) {
    let text = eTextNodes[i];
    if(text === ec) {
      eidx += eo;
      break;
    } else {
      eidx += text.nodeValue.length;
    }
  }
  return [sidx, eidx];
}

export {
  getParaListFromNodeOrFrag,
  getTextNodesFromNodeOrFrag,
  getContentNodesFromNodeOrFrag,
  getContentNodesFromRange,
  getTextNodesFromRange,
  getTextFromNodeOrFrag,
  getBoundLetterFromRange,
  getTextIndexBaseParaFromRange
}